#include "Frustum.h"
#include "LuaScript.h"

Frustum::Frustum(void)
{
	lua = LuaScript::getInstance();
	lua->LoadFile("../init.lua");

	up.x = 0.0f;
	up.y = 1.0f;
	up.z = 0.0f;
	angle = 0;
	mRot = 0;

	farDist = lua->getVarf("far_distance");
	nearDist = lua->getVarf("near_distance");

	fovy = lua->getVarf("field_of_view_y");
	aspectratio = lua->getVarf("aspect_ratio");

	fPos = vec3(0,0,0);
	fDir = vec3(0,0,3.14159f);
	fUp = vec3(0,1,0);

	bSetFrustum = true;
}

Frustum::~Frustum(void)
{
}

bool Frustum::Init(void)
{
	gluPerspective(fovy, aspectratio, nearDist, farDist);

	return true;
}

void NormalizePlane(glm::vec4 frustum[6], int side)
{
	// Here we calculate the magnitude of the normal to the plane (point A B C)
	// Remember that (A, B, C) is that same thing as the normal's (X, Y, Z).
	// To calculate magnitude you use the equation:  magnitude = sqrt( x^2 + y^2 + z^2)
	float magnitude = (float)sqrt( frustum[side].x * frustum[side].x + 
								   frustum[side].y * frustum[side].y + 
								   frustum[side].z * frustum[side].z );

	// Then we divide the plane's values by it's magnitude.
	// This makes it easier to work with.
	frustum[side].x /= magnitude;
	frustum[side].y /= magnitude;
	frustum[side].z /= magnitude;
	frustum[side].w /= magnitude; 
}

// Update the frustum
void Frustum::Update(void)
{
	heightNear = 2 * tan(fovy*0.5f) * nearDist;
	heightFar  = 2 * tan(fovy*0.5f) * farDist;

	widthNear = heightNear * aspectratio;
	widthFar  = heightFar * aspectratio;

	farC = fPos + (fDir.normal() * farDist);
	nearC = fPos + (fDir.normal() * nearDist);
	
	right = (fDir.normal() ^ up );

	ftl = farC + up * (heightFar*0.5f) - right * (widthFar * 0.5f);
	ftr = farC + up * (heightFar*0.5f) + right * (widthFar * 0.5f);
	fbl = farC - up * (heightFar*0.5f) - right * (widthFar * 0.5f);
	fbr = farC - up * (heightFar*0.5f) + right * (widthFar * 0.5f);

	ntl = nearC + up * (heightNear*0.5f) - right * (widthNear * 0.5f);
	ntr = nearC + up * (heightNear*0.5f) + right * (widthNear * 0.5f);
	nbl = nearC - up * (heightNear*0.5f) - right * (widthNear * 0.5f);
	nbr = nearC - up * (heightNear*0.5f) + right * (widthNear * 0.5f);

	vec3 v1,v2;

	//updating farplane Vector
	v1 = fbl - ftl;
	v2 = ftr - ftl;
	farV = (v1 ^ v2).normal();

	//updating nearplane Vector
	v1 = nbl - ntl;
	v2 = ntr - ntl;
	nearV = (v1 ^ v2).normal();

	//updating btmplane Vector
	v1 = nbl - fbl;
	v2 = fbr - fbl;
	btmV = (v1 ^ v2).normal();

	//updating topplane Vector
	v1 = ntl - ftl;
	v2 = ftr - ftl;
	topV = (v1 ^ v2).normal();

	//updating leftPlane Vector
	v1 = nbl - ntl;
	v2 = ftl - ntl;
	leftV = (v1 ^ v2).normal();

	//updating rightPlaneVector
	v1 = ftr - ntr;
	v2 = nbr - ntr;
	rightV = (v1 ^ v2).normal();

	FrustumPlanes[FTL] = ftl;
	FrustumPlanes[FTR] = ftr;
	FrustumPlanes[FBL] = fbl;
	FrustumPlanes[FBR] = fbr;
	FrustumPlanes[NTL] = ntl;
	FrustumPlanes[NTR] = ntr;
	FrustumPlanes[NBL] = nbl;
	FrustumPlanes[NBR] = nbr;

	FrustumNormals[FARV] = farV;
	FrustumNormals[NEARV] = nearV;
	FrustumNormals[LEFTV] = leftV;
	FrustumNormals[RIGHTV] = rightV;
	FrustumNormals[TOPV] = topV;
	FrustumNormals[BTMV] = btmV;
}

// Draw the frustum
void Frustum::Draw(void)
{
	if(!bSetFrustum) return;

	glPushMatrix();
//	glTranslatef(fPos.x, fPos.y, fPos.z);

		glPushAttrib(GL_ENABLE_BIT);
			glEnable( GL_BLEND ); glDisable(GL_CULL_FACE);
			glBlendFunc( GL_SRC_ALPHA , GL_ONE_MINUS_SRC_ALPHA );
			glColor4f( 0.0f, 0.0f, 1.0f, 0.3f);
			glBegin( GL_QUADS );
			// left face
				glVertex3f(nbl.x, nbl.y, nbl.z);
				glVertex3f(ntl.x, ntl.y, ntl.z);
				glVertex3f(ftl.x, ftl.y, ftl.z);
				glVertex3f(fbl.x, fbl.y, fbl.z);

			// right face			
				glVertex3f(nbr.x, nbr.y, nbr.z);
				glVertex3f(ntr.x, ntr.y, ntr.z);
				glVertex3f(ftr.x, ftr.y, ftr.z);
				glVertex3f(fbr.x, fbr.y, fbr.z);

			// front face
				glVertex3f(ftl.x, ftl.y, ftl.z);
				glVertex3f(ftr.x, ftr.y, ftr.z);
				glVertex3f(fbr.x, fbr.y, fbr.z);
				glVertex3f(fbl.x, fbl.y, fbl.z);

			// top face
				glVertex3f(ntl.x, ntl.y, ntl.z);
				glVertex3f(ntr.x, ntr.y, ntr.z);
				glVertex3f(ftr.x, ftr.y, ftr.z);
				glVertex3f(ftl.x, ftl.y, ftl.z);

			// bottom face
				glVertex3f(nbl.x, nbl.y, nbl.z);
				glVertex3f(nbr.x, nbr.y, nbr.z);
				glVertex3f(fbr.x, fbr.y, fbr.z);
				glVertex3f(fbl.x, fbl.y, fbl.z);
			
			glEnd();
			glDisable( GL_BLEND );
		glPopAttrib();

			// Draw the lines of the Frustum
			glColor4f(1.0f, 1.0f, 1.0f, .4f);
			glLineWidth(5);
			glBegin( GL_LINES );
				glVertex3f(ntl.x, ntl.y, ntl.z);
				glVertex3f(ntr.x, ntr.y, ntr.z);

				glVertex3f(ntr.x, ntr.y, ntr.z);
				glVertex3f(nbr.x, nbr.y, nbr.z);

				glVertex3f(nbr.x, nbr.y, nbr.z);
				glVertex3f(nbl.x, nbl.y, nbl.z);

				glVertex3f(nbl.x, nbl.y, nbl.z);
				glVertex3f(ntl.x, ntl.y, ntl.z);

			
				glVertex3f(ftl.x, ftl.y, ftl.z);
				glVertex3f(ftr.x, ftr.y, ftr.z);

				glVertex3f(ftr.x, ftr.y, ftr.z);
				glVertex3f(fbr.x, fbr.y, fbr.z);

				glVertex3f(fbr.x, fbr.y, fbr.z);
				glVertex3f(fbl.x, fbl.y, fbl.z);

				glVertex3f(fbl.x, fbl.y, fbl.z);
				glVertex3f(ftl.x, ftl.y, ftl.z);


				glVertex3f(ntl.x, ntl.y, ntl.z);
				glVertex3f(ftl.x, ftl.y, ftl.z);
			
				glVertex3f(ntr.x, ntr.y, ntr.z);
				glVertex3f(ftr.x, ftr.y, ftr.z);

				glVertex3f(nbl.x, nbl.y, nbl.z);
				glVertex3f(fbl.x, fbl.y, fbl.z);

				glVertex3f(nbr.x, nbr.y, nbr.z);
				glVertex3f(fbr.x, fbr.y, fbr.z);
			glEnd();
			glLineWidth(1);

	glPopMatrix();
}

// Perform containment check for a position with respect to the Frustum
bool Frustum::PointInFrustum(vec3 position)
{
	vec3 pointToPos;
	float angle;

	//checking against far plane
	pointToPos = position - ftl;
	angle = pointToPos.normal() * farV;
	if (angle < 0) return false;

	//checking against near plane
	pointToPos = position - ntl;
	angle = pointToPos.normal() * nearV;
	if (angle > 0) return false;

	//checking against btm plane
	pointToPos = position - fbl;
	angle = pointToPos.normal() * btmV;
	if (angle < 0) return false;

	//checking against top plane
	pointToPos = position - ftl;
	angle = pointToPos.normal() * topV;
	if (angle > 0) return false;

	//checking against left plane
	pointToPos = position - ntl;
	angle = pointToPos.normal() * leftV;
	if (angle < 0) return false;
	
	//checking against right plane
	pointToPos = position - ntr;
	angle = pointToPos.normal() * rightV;
	if (angle < 0) return false;

	return true;
}

// Pass in an array of points
// Check if the points are inside the frustum
// If bCheckAll is true, return true only when all points are inside frusutm
// Else, return true when just one point is inside the frusutm
bool Frustum::PointsInFrustum(vec3 *p, int ArrSize, bool bCheckAll)
{
	if(!p) return false;

	vec3 pointToPos;
	float angle;

	for(int i = 0; i < ArrSize; ++i)
	{
		//checking against far plane
		pointToPos = p[i] - ftl;
		angle = pointToPos.normal() * farV;	
		if (angle < 0)
		{
			if(bCheckAll) return false;
			else continue;
		}

		//checking against near plane
		pointToPos = p[i] - ntl;
		angle = pointToPos.normal() * nearV;
		if (angle > 0)
		{
			if(bCheckAll) return false;
			else continue;
		}

		//checking against btm plane
		pointToPos = p[i] - fbl;
		angle = pointToPos.normal() * btmV;
		if (angle < 0)
		{
			if(bCheckAll) return false;
			else continue;
		}

		//checking against top plane
		pointToPos = p[i] - ftl;
		angle = pointToPos.normal() * topV;
		if (angle > 0)
		{
			if(bCheckAll) return false;
			else continue;
		}

		//checking against left plane
		pointToPos = p[i] - ntl;
		angle = pointToPos.normal() * leftV;
		if (angle < 0)
		{
			if(bCheckAll) return false;
			else continue;
		}
	
		//checking against right plane
		pointToPos = p[i] - ntr;
		angle = pointToPos.normal() * rightV;
		if (angle < 0)
		{
			if(bCheckAll) return false;
			else continue;
		}

		if(!bCheckAll) return true;
	}

	return (bCheckAll) ? true : false;
}

bool Frustum::IntersectsSphere(vec3 pos, float r)
{
	return PointInFrustum(pos + r ) || PointInFrustum(pos - r);
}

bool Frustum::ContainsSphere(vec3 pos, float r)
{
	return PointInFrustum(pos + r ) && PointInFrustum(pos - r);
}

bool Frustum::IntersectsCube(vec3 pos, float size)
{
	size /= 2;
	vec3 points[8];
	points[0] = pos + vec3(size);
	points[1] = pos + vec3(-size);
	points[2] = pos + vec3(size,-size,size);
	points[3] = pos + vec3(size,size,-size);
	points[4] = pos + vec3(size,-size,-size);
	points[5] = pos + vec3(-size,size,size);
	points[6] = pos + vec3(-size,-size,size);
	points[7] = pos + vec3(-size,size,-size);
	return PointsInFrustum(points,8,false);
}

bool Frustum::ContainsCube(vec3 pos, float size)
{
	size /= 2;
	vec3 points[8];
	points[0] = pos + vec3(size);
	points[1] = pos + vec3(-size);
	points[2] = pos + vec3(size,-size,size);
	points[3] = pos + vec3(size,size,-size);
	points[4] = pos + vec3(size,-size,-size);
	points[5] = pos + vec3(-size,size,size);
	points[6] = pos + vec3(-size,-size,size);
	points[7] = pos + vec3(-size,size,-size);
	return PointsInFrustum(points,8,true);
}

bool Frustum::IntersectsBox(vec3 tl, vec3 br)
{
	vec3 points[8];
	points[0] = tl;
	points[1] = br;
	points[2] = vec3(br.x,tl.y,tl.z);
	points[3] = vec3(tl.x,br.y,tl.z);
	points[4] = vec3(tl.x,tl.y,br.z);
	points[5] = vec3(br.x,br.y,tl.z);
	points[6] = vec3(br.x,tl.y,br.z);
	points[7] = vec3(tl.x,br.y,br.z);

	return PointsInFrustum(points,8,false);
}

bool Frustum::ContainsBox(vec3 tl, vec3 br)
{
	vec3 points[8];
	points[0] = tl;
	points[1] = br;
	points[2] = vec3(br.x,tl.y,tl.z);
	points[3] = vec3(tl.x,br.y,tl.z);
	points[4] = vec3(tl.x,tl.y,br.z);
	points[5] = vec3(br.x,br.y,tl.z);
	points[6] = vec3(br.x,tl.y,br.z);
	points[7] = vec3(tl.x,br.y,br.z);

	return PointsInFrustum(points,8,true);
}